#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stddef.h>

#include "server.h"

#define QLEN        10


static void err_sys(const char* str) {
    fprintf(stderr, "%s: %s\n", str, strerror(errno));
    exit(1);
}

// 通过name字符串, 创建sock fd, 绑定&创建指定文socket文件, 监听客户进程请求. 
// 返回sock fd.
int serv_listen(const char *name) {
    int fd, len;
    struct sockaddr_un un;

    if (strlen(name) >= sizeof (un.sun_path)) {
        errno = ENAMETOOLONG;
        return -1;
    }

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        return -2;
    }


    // 如果存在就移除name对应文件, 避免bind无法创建socket文件而导致调用失败
    unlink(name);


    memset(&un, 0, sizeof (un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name); 

    // 计算un实际所用到的字节数
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    // bind name到文件描述符fd
    if (bind(fd, (struct sockaddr *)&un, len) < 0) {
        close(fd);
        return -3;
    }

    if (listen(fd, QLEN) < 0) {
        close(fd);
        return -4;
    }

    return fd;
}

// 接收客户进程请求, 并建立连接
int serv_accept(int listenfd, uid_t *uidptr) {
    struct sockaddr_un un;
    int clifd; // 客户进程Unix Domain套接字
    socklen_t len; // sock文件路径长度(不含null终结符)
    char *name;

    // 多出1byte空间用于存放null终结符
    if ((name = malloc(sizeof (un.sun_path) + 1)) == NULL) 
        return -1;

    len = sizeof(un);

    // 接受客户进程连接请求, un会包含客户进程连接信息, clifd是对应用客户进程sock fd
    if ((clifd = accept(listenfd,(struct sockaddr *) &un, &len)) < 0) {
        free(name);
        return -2;
    }

    // 从un读取sock文件名
    len -= offsetof(struct sockaddr_un, sun_path); // sun_path长度 = len - 偏移
    memcpy(name, un.sun_path, len);
    name[len] = 0; // 确保末尾为null终结符

    // 读取文件状态
    struct stat statbuf;
    if (stat(name, &statbuf) < 0) {
        close(listenfd);
        free(name);
        return -3;
    }

    // 检查文件类型是否为sock
#ifdef S_ISSOCK
    if (S_ISSOCK(statbuf.st_mode) == 0) {
        // 非sock文件
        close(listenfd);
        free(name);
        return -4;
    }
#endif

    // 检查文件访问权限
    // 要求为rwx------
    if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) || (statbuf.st_mode & S_IRWXU) != S_IRWXU) {
        close(listenfd);
        free(name);
        return -5;
    }

#define STALE       30

    // 检查文件相关时间不比当前时间早30秒
    int lasttime = time(NULL) - STALE;
    if (statbuf.st_atime < lasttime || 
        statbuf.st_mtime < lasttime || 
        statbuf.st_ctime < lasttime) {
        close(listenfd);
        free(name);
        return -6;
    }

    // 将sock文件uid赋值给uidptr
    if (uidptr != NULL) {
        *uidptr = statbuf.st_uid;
    }

    // 与客户进程的连接已经成功建立, 不再需要sock文件
    unlink(name);
    free(name);

    return clifd;
}


